# coding: utf-8
# @Author: 莫小鱼
import operator
import allure
import jsonpath
from typing import Callable, Any
from AutoApiTest.unit_tools.db_connector.connectMysql import ConnectMysql
from AutoApiTest.unit_tools.log_util import logs
from AutoApiTest.unit_tools.exception_utils.exceptions import AssertTypeError


class Assertions:
    """
    接口断言模式封装
    1） 装态码断言
    2） 包含断言
    3） 相等断言
    4） 不相等断言
    5） 数据库断言
    6） schema断言
    """

    @classmethod
    def status_code_assert(cls, expected_result, status_code):
        """
        接口响应码断言
        :param expected_result: yaml文件中code
        :param status_code: 接口实际返回的状态码
        :return:
        """
        # 断言状态标识，0表示成功，其他表示失败
        failure_count = 0
        if not isinstance(expected_result, int):
            expected_result = int(expected_result)
        if expected_result == status_code:
            logs.info(f"状态码断言成功：实际返回状态码：{status_code} == {expected_result}")
            allure.attach(f"预期结果：{str(expected_result)}\n实际结果：{str(status_code)}","状态码断言结果：成功",
                          attachment_type=allure.attachment_type.TEXT)
        else:
            logs.info(f"状态码断言失败：实际返回状态码：{status_code} != {expected_result}")
            failure_count += 1
            allure.attach(f"预期结果：{str(expected_result)}\n实际结果：{str(status_code)}", "状态码断言结果：失败",
                          attachment_type=allure.attachment_type.TEXT)

        return failure_count

    @classmethod
    def contain_assert(cls, expected_result, response):
        """
        字符串包含模式，断言预期结果字符串是否包含在接口实际响应信息中
        :param expected_result: yaml文件中contain数据
        :param response: (dict)接口实际响应
        :return:
        """
        failure_count = 0
        for assert_key, assert_value in expected_result.items():
            response_list = jsonpath.jsonpath(response, f'$..{assert_key}')
            if response_list and isinstance(response_list[0], str):
                response_str = ''.join(response_list)

                success_message = f"包含模式断言成功：预期结果【{assert_value}】存在于【{response_str}】"
                failure_message = f"包含模式断言失败：预期结果【{assert_value}】不存在于【{response_str}】"
                if assert_value in response_str:
                    logs.info(success_message)
                    allure.attach(f"预期结果：{assert_value}\n实际结果：{response_str}", "字段包含断言结果：包含",
                                  attachment_type=allure.attachment_type.TEXT)
                else:
                    failure_count += 1
                    logs.error(failure_message)
                    allure.attach(f"预期结果：{assert_value}\n实际结果：{response_str}", "字段包含断言结果：不包含",
                                  attachment_type=allure.attachment_type.TEXT)
        return failure_count

    @classmethod
    def equal_assert(cls, expected_result, response):
        """
        相等断言，根据yaml里面eq字段的值与实际响应比对
        :param expected_result: (dict) yaml里面eq
        :param response: (dict)接口实际结果
        :return:
        """
        failure_count = 0
        if isinstance(response, dict) and isinstance(expected_result, dict):

            # 找出响应结果与预期结果共同的key值
            common_key = list(expected_result.keys() & response.keys())
            if common_key:
                common_key = common_key[0]
                new_actual_result = {common_key: response[common_key]}
                eq_assert = operator.eq(new_actual_result, expected_result)
                if eq_assert:
                    logs.info(f"yaml文件的预期结果{expected_result}等于接口响应结果{new_actual_result}")
                    allure.attach(f"预期结果：{str(expected_result)}\n实际结果：{str(new_actual_result)}", "相等断言结果：相等",
                                  attachment_type=allure.attachment_type.JSON)
                else:
                    failure_count += 1
                    logs.error(f"yaml文件的预期结果{expected_result}不等于接口响应结果{new_actual_result}")
                    allure.attach(f"预期结果：{str(expected_result)}\n实际结果：{str(new_actual_result)}", "相等断言结果：不相等",
                                  attachment_type=allure.attachment_type.JSON)
            else:
                failure_count += 1
                logs.info("相等断言失败；请检查yaml文件eq值和接口响应是否正确")

        return failure_count

    @classmethod
    def not_equal_assert(cls, expected_result, response):
        """
        不相等断言，根据yaml里面ne字段的值与实际响应比对
        :param expected_result: (dict) yaml里面ne
        :param response: (dict)接口实际结果
        :return:
        """
        failure_count = 0
        if isinstance(response, dict) and isinstance(expected_result, dict):

            # 找出响应结果与预期结果共同的key值
            common_key = list(expected_result.keys() & response.keys())
            if common_key:
                common_key = common_key[0]
                new_actual_result = {common_key: response[common_key]}
                eq_assert = operator.ne(new_actual_result, expected_result)
                if eq_assert:
                    logs.info(f"yaml文件的预期结果{expected_result}不等于接口响应结果{new_actual_result}")
                    allure.attach(f"预期结果：{str(expected_result)}\n实际结果：{str(new_actual_result)}", "不相等断言结果：不相等",
                                  attachment_type=allure.attachment_type.JSON)
                else:
                    failure_count += 1
                    logs.error(f"yaml文件的预期结果{expected_result}等于接口响应结果{new_actual_result}")
                    allure.attach(f"预期结果：{str(expected_result)}\n实际结果：{str(new_actual_result)}", "不相等断言结果：相等",
                                  attachment_type=allure.attachment_type.JSON)
            else:
                failure_count += 1
                logs.info("不相等断言失败；请检查yaml文件eq值和接口响应是否正确")
        return failure_count

    @classmethod
    def database_assert(cls, expected_result, status_code=None):
        """
        数据库断言
        :param expected_result: yaml中db字段中的sql语句结果值
        :param status_code:
        :return:
        """
        failure_count = 0
        conn = ConnectMysql()
        db_value = conn.query(expected_result)
        if db_value is not None:
            logs.info(f"数据库断言成功！数据为：【{db_value}】")
            allure.attach(f"预期结果：{str(expected_result)}\n实际结果：{str(db_value)}", "数据库断言结果：成功",
                          attachment_type=allure.attachment_type.JSON)
        else:
            failure_count += 1
            logs.error(f"数据库断言失败！！！请检查sql语句{expected_result}")
            allure.attach(f"预期结果：{str(expected_result)}\n实际结果：{str(db_value)}", "数据库断言结果：失败",
                          attachment_type=allure.attachment_type.JSON)
        return failure_count

    @classmethod
    def assert_result(cls, expected_result, response, status_code):
        """
        断言主函数，通过all_flag == 0 表示成功，否则为失败
        :param expected_result: （list）yaml文件中validation关键字下面的预期结果
        :param response: 实际响应信息
        :param status_code: 实际响应结果
        :return:
        """
        all_flag = 0
        assert_methods = {
            'code': cls.status_code_assert,
            'contain': cls.contain_assert,
            'eq': cls.equal_assert,
            'ne': cls.not_equal_assert,
            'db': cls.database_assert,
        }
        try:
            for yq in expected_result:
                for assert_mode, assert_value in yq.items():
                    assert_method: Callable[[Any, Any], int] = assert_methods.get(assert_mode)
                    if assert_method:
                        # 调用对应断言方法
                        if assert_mode in ['code', 'db']:
                            flag = assert_method(assert_value, status_code)
                        else:
                            flag = assert_method(assert_value, response)
                        all_flag += flag
                    else:
                        logs.error(f"不支持该断言模式{assert_mode}")
                        raise AssertTypeError(f"不支持该断言模式{assert_mode}")

        except Exception as exception:
            logs.error(f"断言异常：--{exception}--")
            raise exception

        assert all_flag == 0, '测试失败'
        logs.info("测试成功")
